package com.leisurexi.gedis.client.codec;

import com.leisurexi.gedis.client.protocol.Packet;
import com.leisurexi.gedis.client.protocol.RequestProtocol;
import com.leisurexi.gedis.client.protocol.ResponseProtocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 编解码数据
 *
 * @author: leisurexi
 * @date: 2020-09-08 11:22
 */
@Slf4j
public class PacketCodec {

    public static final PacketCodec INSTANCE = new PacketCodec();

    private PacketCodec() {
    }

    /**
     * 魔数
     */
    public static final String HEADER = "Gedis";

    /**
     * 将 {@link Packet} 封装成二进制数据的过程
     * 1. 首先，调用 Netty 的 ByteBuf 分配器来创建，ioBuffer() 方法
     * 会返回适配 io 读写相关的内存，它会尽可能创建一个直接内存，直接内存
     * 可以理解为不受 jvm 堆管理的内存空间，写到 io 缓冲区的效果更高。
     * 2. 接下来，将 Java 对象序列化成二进制数据包。
     * 3. 最后，对照协议的设计，逐个往 ByteBuf 写入字段，实现编码过程。
     *
     * @param buffer Netty 封装的二进制包装对象
     * @param packet 数据包对象
     */
    public void encode(ByteBuf buffer, Packet packet) {
        RequestProtocol requestProtocol = (RequestProtocol) packet;
        buffer.writeBytes(HEADER.getBytes(StandardCharsets.UTF_8));
        buffer.writeByte(requestProtocol.getVersion());
        buffer.writeByte(requestProtocol.getCommand());
        List<String> args = requestProtocol.getArgs();

        ByteBuf tempBuffer = Unpooled.buffer();
        if (!CollectionUtils.isEmpty(args)) {
            int argsCount = args.size();
            tempBuffer.writeByte(argsCount);
            args.stream().forEach(s -> {
                byte[] arg = s.getBytes(StandardCharsets.UTF_8);
                tempBuffer.writeInt(arg.length);
                tempBuffer.writeBytes(arg);
            });
        }

        byte[] data = ByteBufUtil.getBytes(tempBuffer);
        buffer.writeInt(data.length);
        buffer.writeBytes(data);
    }

    /**
     * 解码流程如下：
     * 1. 首四个字节是我们定义的魔数 0x12345678，这里调用 skipBytes() 跳过这四个字节。
     * 2. 这里，暂时不关注协议版本，通常我们在没有遇到协议升级的时候，这个字段暂时不处理，
     * 因为，你会发现，绝大多数情况下，这个字段几乎用不着，但仍然需要留着。
     * 3. 接下来，调用 ByteBuf 的 API 分别拿到序列化算法标识、指令、数据包的长度。
     * 4. 最后，根据拿到的数据包的长度取出数据，通过指令拿到该数据包对应的 Java 对象的类型，
     * 根据序列化算法标识拿到序列化对象，将字节数组转化为 Java 对象，至此，解码过程结束。
     *
     * @param byteBuf 编码后的数据
     * @return 解码后的数据包对象
     */
    public ResponseProtocol decode(ByteBuf byteBuf) {
        byte result = byteBuf.readByte();

        // 版本号
        byte version = byteBuf.readByte();
        // 指令
        byte command = byteBuf.readByte();
        // 数据报长度
        int length = byteBuf.readInt();
        byte[] bytes = null;
        if (length > 0) {
            bytes = new byte[length];
            byteBuf.readBytes(bytes);
        }
        return ResponseProtocol.builder()
                .result(result)
                .version(version)
                .command(command)
                .dataLen(length)
                .data(length > 0 ? new String(bytes) : null)
                .build();
    }

}
